package com.ljw.spring.annotation.conf;

import com.ljw.spring.annotation.bean.*;
import com.ljw.spring.annotation.condition.LinuxCondition;
import com.ljw.spring.annotation.condition.WindowsCondition;
import com.ljw.spring.annotation.filter.MyTypeFilter;
import com.ljw.spring.annotation.selector.MyImportBeanDefinitionRegistrar;
import com.ljw.spring.annotation.selector.MyImportSelector;
import com.ljw.spring.annotation.service.UserService;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import sun.plugin2.os.windows.Windows;

import javax.sql.rowset.CachedRowSet;

/**
 * @FileName MyConfig
 * @Description TODO spring注解开发
 * @Author ljw
 * @Date 2021/2/1 3:33
 * @Version 1.0
 */
@Configuration
@ComponentScan(
        basePackages = {"com.ljw.spring"},
        includeFilters = {
                /*@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class}),
                @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,classes = {UserService.class}),*/
                @ComponentScan.Filter(type = FilterType.CUSTOM,classes = {MyTypeFilter.class})
        },useDefaultFilters = false
 )
//basePackages:指定要扫描的包
//includeFilters=Filter[]:指定扫描时按照哪些过滤规则，将符合过滤规则的类的实例对象加入IOC容器
//excludeFilters=Filter[]:指定扫描时按照哪些过滤规则，将符合过滤规则的类的实例对象排除出IOC容器
//useDefaultFilters = false：禁用默认过滤规则，默认true
//type=FilterType.ANNOTATION:按照注解过滤。被指定注解标注的类的实例对象将被加入或排除出IOC容器
//type =FilterType.ASSIGNABLE_TYPE：按照给定的类型过滤。指定类型的类及其子类的实例对象将被加入IOC容器（接口不能实例化，所以不能加入IOC容器）
//type =FilterType.CUSTOM:按照自定义规则过滤。
//classes={...}：具体的过滤规则内容
/**
 * 向IOC容器注册组件方式：
 * 1. 包扫描（@ComponentScan） + 组件标注注解（@Service、@Controller、@Component等）【应用于自己写的类】
 * 2. @Bean【导入第三方包里面的组件】
 * 3. @Import【快速导入多个组件：第三方组件或自己写的类。单个导入或通过ImportSelector接口的实现类中方法的逻辑来组团导入或手动注册bean】
 *    @Import向IOC容器注册bean组件时，bean的id默认是注册类的全类名，如com.ljw.spring.annotation.bean.Color
 *  （1）单个导入：如@Import(value = {Color.class, Car.class}
 *  （2）组团导入组件：实现ImportSelector接口，重写方法selectImports()：返回需要导入的组件的 全类名数组
 *  （3）手动注册bean：实现接口ImportBeanDefinitionRegistrar，重写方法registerBeanDefinitions():方法内用BeanDefinitionRegistry手动注册bean
 * 4. 使用spring提供的FactoryBean接口， 实现FactoryBean接口，重写方法getObject()注册组件【需要在配置类中用@Bean注册FactoryBean接口的实现类】
 *      （1）从IOC容器获取FactoryBean接口的实现类（如ColorFactoryBean）的实例对象（假如：在IOC容器中bean名为【colorFactoryBean】），
 *             则colorFactoryBean.getClass方法获取的是getObject()方法注册的bean的类型，不是ColorFactoryBean的类型；
 *      （2）要想获取ColorFactoryBean自身的类型，则需要在IOC容器中bean名“colorFactoryBean”前添加前缀“&”。
 *          Object colorFactoryBean3 = context.getBean("&colorFactoryBean");
 *      FactoryBean接口常用于spring框架和其他框架整合
 */
@Import(value = {Color.class, Car.class, MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
public class MyConfig {


    /** @Scope(value = "")的value取值：
     * @see ConfigurableBeanFactory#SCOPE_PROTOTYPE  singleton：默认单实例
     * @see ConfigurableBeanFactory#SCOPE_SINGLETON  prototype： 多实例
     *  singleton：默认单实例。IOC容器启动就调用方法创建实例并将实例放到IOC容器中，以后每次使用时就直接从IOC容器（map.get(beanId)）中获取那个唯一的实例对象
     *  prototype： 多实例。
     *              （1）IOC容器启动并不会调用方法创建实例放到IOC容器中，当每次需要使用实例时，都会去调用方法创建自己那一次需要的实例对象
     *              （2）ioc容器关闭时，多实例的bean不会自动执行销毁方法，而单实例的bean会自动执行销毁方法
     *
     *  @Lazy：懒加载，只针对单实例对象， 即@Scope(value = "singleton")。
     *          作用：使IOC容器启动时不会调用方法创建实例对象，而是在第一次使用实例对象时，才调用（自始至终只调用一次）方法创建唯一的实例对象并放入IOC容器中
     * @return
     */
    @Scope(value = "singleton")
    @Lazy
    // 給容器註冊一個bean,代替spring.xml中的<bean>
    // 类型为返回值的类型，id默认用方法名作为id,也可以用value属性设置id
    @Bean(value = {"user"})
    public User getUser() {
        System.out.println("创建实例");
        return new User("ljw");
    }


    /**
     * 问题1：
     *      如果是window操作系统，则向IOC容器中注册@Bean(value = {"xiyouji"})
     *      如果是linux操作系统，则向IOC容器中注册@Bean(value = {"shiji"})
     * 解答：
     *      @Conditional(value = {WindowsCondition.class})
     *      @Conditional(value = {LinuxCondition.class})
     *
     */
    /**
     * @Conditional: 可标注在类或方法上。必须所有条件满足，才能注册组件到IOC容器。
     * 若标注在类上，表示当满足条件时，这个类中所有的bean注册才会生效
     * @return
     */
    @Conditional(value = {WindowsCondition.class})
    @Bean(value = {"xiyouji"})
    public Book getBook1(){
        return new Book("西游记");
    }


    @Conditional(value = {LinuxCondition.class})
    @Bean(value = {"shiji"})
    public Book getBook2(){
        return new Book("史记");
    }

    @Bean(value = {"colorFactoryBean"})
    public ColorFactoryBean getColorFactoryBean(){
        return new ColorFactoryBean();
    }
}
